package cn.edu.buaa.cnsatm.traffic_big_data_server.util;

import org.apache.activemq.artemis.api.core.Pair;

import javax.vecmath.Point2d;
import java.awt.*;
import java.util.List;
import cn.edu.buaa.cnsatm.traffic_big_data_server.util.Point2D;

public class ComputeGeometryUtil {
    /**
     * @author: LX
     * @className: ComputeGeometryUtils
     * @packageName:
     * @description: 存放计算几何相关的算法
     * @data: 2019-10-09
     **/
    static final double EARTH_RADIUS = 6381372;
    static final double PERIMETER_OF_EARTH = 2 * Math.PI * 6381372;
    static final double LENGTH_OF_MILLER_X = PERIMETER_OF_EARTH;
    static final double LENGTH_OF_MILLER_Y = LENGTH_OF_MILLER_X / 2;
    static final double MILLER_CONSTANT = 2.3;
    public static double getCrossProduct(Point2D a, Point2D b){
        /**
         * @author:  LX
         * @methodsName: getCrossProduct
         * @description: 计算向量a与b的叉积
         * @return: double类型的叉积值
         */
        return a.getX() * b.getY() - b.getX() * a.getY();
    }

    public static Point2D getFootPoint(Point2D p, Pair<Point2D, Point2D> line){
        /**
         * @author:  LX
         * @methodsName: getCrossProduct
         * @description: 获取点p到t与s所在直线的垂足,s+(t-s)*(dot(p-s,t-s)/len(t-s)^2)
         * @return: 垂足点
         */
        Point2D footPointPart1 = new Point2D(line.getB().getX(), line.getB().getY());
        Point2D footPointPart2 = new Point2D(line.getA().getX(), line.getA().getY());
        footPointPart2 = footPointPart2.subtract(line.getB());//t-s
        Point2D footPointPart3 = new Point2D(p.getX(), p.getY());
        footPointPart3 = footPointPart3.subtract(line.getB());//p-s
        double dotProduct = footPointPart3.dotProduct(footPointPart3);
        double norm = footPointPart2.getX() * footPointPart2.getX() + footPointPart2.getY() * footPointPart2.getY();
        footPointPart2 = footPointPart2.multiply((dotProduct / norm));
        Point2D footPoint = new Point2D(footPointPart1.getX() + footPointPart2.getX(),
                footPointPart1.getY() + footPointPart2.getY());
        return footPoint;
    }

    public static boolean isPointOnLine(Point2D point, Point2D linePointA, Point2D linePointB){
        /**
         * @author:  LX
         * @methodsName: isPointOnLine
         * @description: 判断point是否在linePointA/B组成的线段上
         */
        double equalLeft  = (point.getX() - linePointA.getX()) * (linePointA.getY() - linePointB.getY());
        double equalRight = (linePointA.getX() - linePointB.getX()) * (point.getY() - linePointA.getY());
        if(equalLeft == equalRight){//通过斜率判断点在直线上
            //判断是否在线段上
           boolean isInRangeLeft  = (Math.min(linePointA.getX(), linePointB.getX()) <= point.getX());
           boolean isInRangeRight = (Math.max(linePointA.getX(), linePointB.getX()) >= point.getX());
           return (isInRangeLeft && isInRangeRight);
        }
        return false;
    }
    public static Double calDistanceByLonAndLa(Point2D a, Point2D b){
        /**
         * @author:  LX
         * @methodsName: calDistanceByLonAndLa
         * @description: 根据经纬度算距离
         * @return: 单位m
         */
            double radiansAX = Math.toRadians(a.getX()); // A经弧度
            double radiansAY = Math.toRadians(a.getY()); // A纬弧度
            double radiansBX = Math.toRadians(b.getX()); // B经弧度
            double radiansBY = Math.toRadians(b.getY()); // B纬弧度

            // 公式中“cosβ1cosβ2cos（α1-α2）+sinβ1sinβ2”的部分，得到∠AOB的cos值
            double cos = Math.cos(radiansAY) * Math.cos(radiansBY) * Math.cos(radiansAX - radiansBX)
                    + Math.sin(radiansAY) * Math.sin(radiansBY);
//        System.out.println("cos = " + cos); // 值域[-1,1]
            double acos = Math.acos(cos); // 反余弦值
//        System.out.println("acos = " + acos); // 值域[0,π]
//        System.out.println("∠AOB = " + Math.toDegrees(acos)); // 球心角 值域[0,180]
            return EARTH_RADIUS * acos; // 最终结果
    }

    public static int isPointInPoly(Point2D point, List<Point2D> poly){
        //TODO:并行提速
        /**
         * @author:  LX
         * @methodsName: isPointInPoly
         * @description: 判断一个点point是否在面poly内，做一条直线，判断线与面的交点个数是否为偶数，目前考虑在边上的也算在面内
         * @return: 点与面的关系状态标记码，0外 , 1内 , 2边上
         * @throws:
         */

        int pointPolyCode = 0;

        for(int i = 0; i < poly.size(); i++) {
            if(isPointOnLine(point, poly.get(i), poly.get((i + 1) % poly.size()))) {return 2;}
            double xA = poly.get((i + 1) % poly.size()).getX() - poly.get(i).getX();
            double yA = poly.get((i + 1) % poly.size()).getY() - poly.get(i).getY();
            double xB = point.getX() - poly.get(i).getX();
            double yB = point.getY() - poly.get(i).getY();
            Point2D pointA = new Point2D(xA, yA);
            Point2D pointB = new Point2D(xB, yB);
            double k = getCrossProduct(pointA, pointB);
            double d1 = poly.get(i).getY() - point.getY();
            double d2 = poly.get((i + 1) % poly.size()).getY() - point.getY();
            if (k > 0 && d1 <= 0 && d2 > 0) pointPolyCode++;
            if (k < 0 && d2 <= 0 && d1 > 0) pointPolyCode--;
        }
        return pointPolyCode % 2;

    }

    public static int isPointInPolyWithHeight(Point2D point, List<Point2D> poly, Double pointHeight, Double polyHeightLowerBound, Double polyHeightUpperBound){
        /**
         * @author:  LX
         * @methodsName: isPointInPolyWithHeight
         * @description: 重载方法，判断一个点是否在面内，考虑高度
         * @param:  point是点，poly是面，pointHeight是的高度，polyHeight(Lower/Upper)Bound分别为面高度上下界
         */
        if(pointHeight <= polyHeightUpperBound && pointHeight >= polyHeightLowerBound){//判断高度是否相符
            return isPointInPoly(point, poly);
        }
        return 0;
    }

    public static Point2D millierConvertion(Point2D point){
        /**
         * @author:  LX
         * @methodsName: millerConvertion
         * @description: 将经纬度转化为平面的米勒坐标
         * @param:  point经纬度坐标组成的点
         * @return: Point2D类型的millerPoint
         * @throws:
         */
        double millerX = point.getX() * Math.PI / 180;//经度转化为弧度
        double millerY = point.getY() * Math.PI / 180;//纬度转化为弧度
        millerY = 1.25 * Math.log(Math.tan(0.25 * Math.PI + 0.4 * millerY));//投影转换
        millerX = LENGTH_OF_MILLER_X * (millerX + Math.PI) / (2 * Math.PI);
        millerY = LENGTH_OF_MILLER_Y * (MILLER_CONSTANT - millerY) / (2 * MILLER_CONSTANT);
        Point2D millerPoint = new Point2D(millerX, millerY);
        return millerPoint;
    }
    public static double getAreaByLonAndLatList(List<Point2D> list){
        /**
         * @author:  LX
         * @methodsName: getAreaByLonAndLatList
         * @description: 求出经纬度点构成的多面形面积
         */
        double area = 0;
        if(list.size() > 2){
            for(int i = 0; i < list.size() - 1; i++){
                Point2D p1 = list.get(i);
                Point2D p2 = list.get(i + 1);
                area += convertToRadian(p2.getX() - p1.getX())
                        * (2 + Math.sin(convertToRadian(p1.getX()))
                        + Math.sin(convertToRadian(p2.getY())));
            }
        }
        return Math.abs(area);
    }
    public static double convertToRadian(double pos){
        return pos * Math.PI / 180.0;
    }

}
